/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ts, NodePredicate, rule } from 'qvog-dsl';

const SEVERITY = 1;
const DOC_PATH = 'docs/explicit-member-accessibility.md';
const DESCRIPTION = 'Require explicit accessibility modifiers on class members';

const ClassMember: NodePredicate<ts.ClassElement> = {
  is(node: ts.Node): node is ts.ClassElement {
    return ts.isMethodDeclaration(node) ||
      ts.isPropertyDeclaration(node) ||
      ts.isConstructorDeclaration(node) ||
      ts.isGetAccessorDeclaration(node) ||
      ts.isSetAccessorDeclaration(node) ||
      ts.isParameterPropertyDeclaration(node, node.parent);
  }
};

const DEFAULT_CONFIG = {
  accessibility: 'explicit' as const,
  overrides: {
    constructors: 'explicit',
    accessors: 'explicit',
    methods: 'explicit',
    properties: 'explicit',
    parameterProperties: 'explicit'
  }
};

interface ruleOption {
  /**
   * 全局访问修饰符检查策略
   * @default 'explicit'
   * 
   * - 'explicit': 必须显式声明 public/private/protected
   * - 'no-public': 禁止使用 public 修饰符
   * - 'off': 禁用检查
   */
  accessibility?: 'explicit' | 'no-public' | 'off';

  /**
   * 忽略检查的方法/属性名称列表
   * @default []
   * 
   * @example 
   * ['internal_', 'secretMethod'] 会跳过名为 internal_ 和 secretMethod 的成员
   */
  ignoredMethodNames?: string[];

  /**
   * 按成员类型覆盖全局配置
   */
  overrides?: {
    /**
     * getter/setter 访问器的特殊配置
     * @default 继承全局 accessibility 配置
     */
    accessors?: 'explicit' | 'no-public' | 'off';

    /**
     * 构造函数的特殊配置
     * @default 继承全局 accessibility 配置
     */
    constructors?: 'explicit' | 'no-public' | 'off';

    /**
     * 普通方法的特殊配置
     * @default 继承全局 accessibility 配置
     */
    methods?: 'explicit' | 'no-public' | 'off';

    /**
     * 类属性的特殊配置
     * @default 继承全局 accessibility 配置
     */
    properties?: 'explicit' | 'no-public' | 'off';

    /**
     * 构造函数参数属性的特殊配置
     * @default 继承全局 accessibility 配置
     * 
     * @example
     * constructor(public id: string) 中的 public id
     */
    parameterProperties?: 'explicit' | 'no-public' | 'off';
  };
}

export default rule<{ context: ruleOption }>()
  .match(ClassMember)
  .when((node: ts.ClassElement, { props }): boolean => {
    const options = props.context || DEFAULT_CONFIG;
    const memberName = getMemberName(node);
    const memberType = getMemberType(node);

    const checkLevel = options.overrides?.[memberType] ?? options.accessibility;

    if (checkLevel === 'off' ||
      memberName.startsWith('#') ||
      isPrivateMember(node) ||
      isIgnoredMethod(options.ignoredMethodNames, memberName)) {
      return false;
    }

    const hasExplicitModifier = hasAccessibilityModifier(node);
    const hasPublicModifier = hasPublicKeyword(node);

    if (checkLevel === 'explicit' && !hasExplicitModifier) {
      return true;
    }

    if (checkLevel === 'no-public' && hasPublicModifier) {
      return true;
    }

    return false;
  })
  .report({
    severity: SEVERITY,
    description: DESCRIPTION,
    docPath: DOC_PATH
  });

function getMemberName(node: ts.ClassElement): string {
  if (ts.isConstructorDeclaration(node)) { return 'constructor' };
  return node.name && ts.isIdentifier(node.name) ? node.name.text : '';
}

function getMemberType(node: ts.ClassElement): keyof typeof DEFAULT_CONFIG.overrides {
  if (ts.isConstructorDeclaration(node)) { return 'constructors' };
  if (ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) { return 'accessors' };
  if (ts.isMethodDeclaration(node)) { return 'methods' };
  if (ts.isPropertyDeclaration(node)) { return 'properties' };
  if (ts.isParameterPropertyDeclaration(node, node.parent)) { return 'parameterProperties' };
  return 'methods'; // fallback
}

function isPrivateMember(node: ts.ClassElement): boolean {
  return !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword);
}

function hasAccessibilityModifier(node: ts.ClassElement): boolean {
  return !!node.modifiers?.some(m =>
    [ts.SyntaxKind.PublicKeyword,
    ts.SyntaxKind.PrivateKeyword,
    ts.SyntaxKind.ProtectedKeyword].includes(m.kind));
}

function hasPublicKeyword(node: ts.ClassElement): boolean {
  return !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.PublicKeyword);
}

function isIgnoredMethod(ignoredNames: string[] = [], name: string): boolean {
  return ignoredNames.includes(name);
}